var HIMEM;
var programCounter;
var carryFlag;
var zeroFlag;
var theAccumulator;
var runMode;
var activeInstructions = new Array();
var programState = { READY: 0, RUN: 1, ERROR: 2 };
var stepCount;

// sensible default values, which could all potentially be overridden
// by the load program operation, and this may have significant UI impact
function init() {
	HIMEM = 160;
	programCounter = 0;
	carryFlag = 0;
	zeroFlag = 0;
	theAccumulator = 0;
	activeInstructions = [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 ];
	runMode = programState.READY;
	stepCount = 0;
}

// a bunch of getters and setters to allow for proper display
// abstraction without the need for global variables
function set_pc(addr) {
	addr = parseInt("0x" + addr);
	if (programCounter < HIMEM) {
		programCounter = addr;
	} else {
		// what is the correct behaviour here ?
		// wrap back to zero, or halt ?
	}
}
function get_pc() {
	return(programCounter);
}

function get_step_count() {
	return(stepCount);
}
function set_step_count(c) {
	stepCount = c;
}
function inc_step_count(c) {
	stepCount += c;
}

function get_HIMEM() {
	return(HIMEM);
}

function set_run_mode(mode) {
	runMode = mode;
}
function get_run_mode() {
	return(runMode);
}

function get_program_state() {
	return(Object.keys(programState)[runMode]);
}

function set_accumulator(value) {
	theAccumulator = value % 16;
}

function get_accumulator() {
	return(theAccumulator);
}

function adc_accumulator(value) {
	theAccumulator += value;
	theAccumulator += get_carry_flag();
	set_carry_flag(theAccumulator);
	theAccumulator = theAccumulator % 16;
	set_zero_flag(theAccumulator);
}

function set_zero_flag(testValue) {
	testValue = parseInt(testValue);
	if (0 == testValue) {
		zeroFlag = 1;
	} else {
		zeroFlag = 0;
	}
}

function get_zero_flag() {
	return(zeroFlag);
}

// want to set the flag when the value is >15, but also
// want a nice clean set0/1 for load and store usage
function set_carry_flag(testValue) {
	testValue = parseInt("0x" + testValue);
	if (testValue > 15) {
		carryFlag = 1;
	} else {
		carryFlag = 0;
	}
}

function get_carry_flag() {
	return(carryFlag);
}

// note that the numerical decode is in the instructions_hex.js file
// and note this is one of the few non-UI functions to return hex (as
// it is destined for the UI eventually)
function decode_mnemonic(theAddr) {
	if (arguments.length == 0) {
		theAddr = get_pc();
	}
	var opcode = activeInstructions[peek(theAddr)];
	var decode = instructionSet[opcode].mnemonic;
	if (2 == instructionSet[opcode].bytes) {
		decode += " " + peek(theAddr + 1).toString(16);
	}
	if (3 == instructionSet[opcode].bytes) {
		var addr = parseInt(peek(theAddr + 2) * 16);
		addr += parseInt(peek(theAddr + 1));
		decode += " " + addr.toString(16);
	}
	return(decode);
}

// this is dodgy, but hard to abstract the reading of the memory locations
// away from the UI and CSS id's unless we back the whole memory with an
// array or other data store, and then copy to/from the array before starting
// any processing - I like the idea of reading the DOM live for the program
// execution as it feels closer to the original Peeko-Computer intention
function peek(addr) {
	var loc = addr;
	if (loc < 16) {
		loc = "0" + loc.toString(16);
	} else {
		loc = loc.toString(16);
	}
	// be careful with live edits - no good answer but prevent console errors
	var the_text = parseInt("0x" + $('#' + loc).text());
	if (isNaN(the_text))
		the_text = "0";
	return(the_text);
}

function poke(address, value) {
	var loc = address;
	if (loc < 16) {
		loc = "0" + loc.toString(16);
	} else {
		loc = loc.toString(16);
	}
	$('#' + loc).text(value.toString(16));
}

/* not sure this is wanted, but it'd be interesting... */
function be_evil() {
	if (Math.random() > 0.6) {
		var loc = Math.floor(Math.random() * get_HIMEM());
		var op = Math.floor(Math.random() * 16);
		$('#' + loc.toString(16)).text(op.toString(16));
	}
}

// input a hex string and pad to 8 characters
function pad_hex(v) {
	var r = v;

	while (r.length < 8) {
		r = "0" + r;
	}
	return(r);
}

/* IO space getters and setters */
function set_port(port, value) {
	var currentLine = $('#outputdisplay').text();
	if (0 == port) {
		// store the chosen value where it can be found again
		$('#p00').text(value.toString(16));
		// what do we do with the current output display ?
		switch(value) {
		// roll left immediate
		case 4:
			var overflow = currentLine.substring(0,1);
			currentLine = currentLine.substring(1,8) + overflow;
			$('#outputdisplay').text(currentLine);
			break;
		// roll right immediate
		case 5:
			var overflow = currentLine.substring(7,8);
			currentLine = overflow + currentLine.substring(0,7);
			$('#outputdisplay').text(currentLine);
			break;
		}
	}

	if (1 == port) {
		// this location is RAZ, so undo anything that might have got in
		$('#p01').text("0");
		// what action should we take on this value ?
		var outputAction = parseInt("0x" + $('#p00').text());
		// what do we do with the current output display ?
		switch(outputAction) {
		// clear to zero and overwrite
		case 0:
			// FIXME: needs to be hex string of fixed length
			var outputString = value.toString(16);
			$('#outputdisplay').text(pad_hex(outputString));
			break;
		// overwrite
		case 1:
			currentLine = currentLine.substring(0,7);
			$('#outputdisplay').text(currentLine + value.toString(16));
			break;
		// shift left and write
		case 2:
		case 4:
			currentLine = currentLine.substring(1,8);
			$('#outputdisplay').text(currentLine + value.toString(16));
			break;
		// shift right and write
		case 3:
		case 5:
			currentLine = "0" + currentLine.substring(0,6);
			$('#outputdisplay').text(currentLine + value.toString(16));
			break;
		}
	}

	if ((port >= 2) && (port <= 9)) {
		$('#p0' + port).removeClass();
		switch(value) {
		case 0:
			//$('#p0' + port).css('background-color', 'red');
			$('#p0' + port).addClass('led_red');
			break;
		case 1:
			//$('#p0' + port).css('background-color', 'lawngreen');
			$('#p0' + port).addClass('led_green');
			break;
		case 2:
			//$('#p0' + port).css('background-color', 'yellow');
			$('#p0' + port).addClass('led_yellow');
			break;
		case 3:
			//$('#p0' + port).css('background-color', 'blue');
			$('#p0' + port).addClass('led_blue');
			break;
		case 4:
			//$('#p0' + port).css('background-color', 'magenta');
			$('#p0' + port).addClass('led_magenta');
			break;
		case 5:
			//$('#p0' + port).css('background-color', 'cyan');
			$('#p0' + port).addClass('led_cyan');
			break;
		case 6:
			//$('#p0' + port).css('background-color', 'white');
			$('#p0' + port).addClass('led_white');
			break;
		case 7:
			//$('#p0' + port).css('background-color', 'lightgrey');
			$('#p0' + port).addClass('led_grey');
			break;
		case 8:
			//$('#p0' + port).css('background-color', 'black');
			$('#p0' + port).addClass('led_black');
			break;
		case 9:
			//$('#p0' + port).css('background-color', 'mediumorchid');
			$('#p0' + port).addClass('led_purple');
			break;
		case 10:
		case 11:
		case 12:
		case 13:
		case 14:
		case 15:
			break;
		}
	}

	if ((port >= 10) && (port <=15)) {
		be_evil();
	}
}

function get_port(port) {
	var answer = 0;
	return answer;
}

// - - - - - - - - - - - - - - - - - - - -

// code for operation outside of the main functions - this is possibly
// all ripe for deprecation once the getters and setters are in place

function halt_program(msg) {
	set_run_mode(msg);
	update_status();
}
function decode_pc() {
	$('#mnemonic').text(decode_mnemonic());
}
function show_pc() {
	var cellID;
	if (get_pc() < 16) {
		cellID = "#0" + get_pc().toString(16);
	} else {
		cellID = "#" + get_pc().toString(16);
	}
	$('#pc').text(get_pc());
	$('.peekotable').find('td').removeClass('highlight');
	$(cellID).addClass('highlight');
}

function show_accumulator() {
	$('#accumulator').text(theAccumulator);
}
function show_carry() {
	$('#carry').text(carryFlag);
}
function show_zero() {
	$('#zero').text(zeroFlag);
}
function show_activity() {
	if (1 == runMode) {
		set_run_mode(programState.RUN);
	} else {
		set_run_mode(programState.READY);
	}
}
function update_status() {
	show_activity();
	show_accumulator();
	show_carry();
	show_zero();
	decode_pc();
}

function get_instruction_set(html, page) {
	var fullSet, pre, post, count = 0;
	if (html) {
		fullSet = '<ul class="instructionlist">';
		pre = "<li>";
		post = "</li>";
	} else {
		fullSet = "";
		pre = "";
		post = "\n";
	}
	for (var i in activeInstructions) {
		fullSet += pre + count.toString(16) + ".&nbsp;&nbsp; ";
		fullSet += instructionSet[activeInstructions[i]].mnemonic;
		fullSet += " (" + instructionSet[activeInstructions[i]].description + ")";
		fullSet += post ;
		count++;
		if (page == count) {
			fullSet += '</ul><ul class="instructionlist">';
		}
	}
	if (html) {
		fullSet += "</ul>";
	}
	return(fullSet);
}

function get_all_instructions(html, page) {
	var fullSet, pre, post, count = 0;
	if (html) {
		fullSet = '<ul class="instructionset">';
		pre = "<li>";
		post = "</li>";
	} else {
		fullSet = "";
		pre = "";
		post = "\n";
	}
	for (var i in instructionSet) {
		fullSet += pre + count.toString(16) + ".&nbsp;&nbsp; ";
		fullSet += instructionSet[i].mnemonic;
		fullSet += " (" + instructionSet[i].description + ")";
		fullSet += post;
		count++;
		if (page == count) {
			fullSet += '</ul><ul class="instructionset">';
		}
	}
	if (html) {
		fullSet += "</ul>";
	}
	return(fullSet);
}

function change_instruction(s, d) {
	if ((s != null) && (d != null)) {
		activeInstructions[s] = d;
	}
}

function change_pc(diff) {
	programCounter += diff;
	inc_step_count(diff);
	if (programCounter < 0)
		programCounter += HIMEM;
	programCounter = programCounter % HIMEM;
}

function execute_pc() {
	var opcode;
	if (programCounter >= HIMEM) {
		halt_program(programState.ERROR);
		return;
	}

	opcode = activeInstructions[peek(get_pc())];
	change_pc(window["execute_" + instructionSet[opcode].mnemonic](instructionSet[opcode].bytes));
}

function save_state(title, description) {
	var mem = new Array();
	for (var hi=0; hi < (HIMEM / 16); hi++) {
		for (var lo=0; lo<16; lo++) {
			mem.push($('#'+hi.toString(16)+lo.toString(16)).text());
		}
	}

	var theState = { "title": title,
			"description": description,
			"HIMEM": get_HIMEM(),
			"programCounter": get_pc(),
			"carryFlag": get_carry_flag(),
			"zeroFlag": get_zero_flag(),
			"theAccumulator": get_accumulator(),
			"activeInstructions": JSON.stringify(activeInstructions),
			"memory": JSON.stringify(mem)
		};
	var allCode = JSON.parse(localStorage.getItem('peekoComputer'));
	if (null == allCode) {
		allCode = new Array();
	}
	var replaced = false;
	for (var i in allCode) {
		if (allCode[i].title == title) {
			allCode[i] = theState;
			replaced = true;
		}
	}
	if (false == replaced) {
		allCode.push(theState);
	}
	localStorage.setItem('peekoComputer', JSON.stringify(allCode));
}

function load_state() {
	halt_program(programState.READY);
	set_HIMEM(JSON.parse(localStorage.getItem('HIMEM')));
	set_pc(JSON.parse(localStorage.getItem('programCounter')));
	set_carry_flag(JSON.parse(localStorage.getItem('carryFlag')));
	set_zero_flag(JSON.parse(localStorage.getItem('zeroFlag')));
	set_accumulator(JSON.parse(localStorage.getItem('theAccumulator')));
	activeInstructions = JSON.parse(localStorage.getItem('activeInstructions'));
	var mem = JSON.parse(localStorage.getItem('memory'));
	for (var hi=0; hi < (HIMEM / 16); hi++) {
		for (var lo=0; lo<16; lo++) {
			$('#'+hi.toString(16)+lo.toString(16)).text(mem.shift());
		}
	}
}
